#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#高级特性Advanced Features
# 1. 切片 Slice
L = list(range(20))

print('L            =', L)
#A list or tuple,if you want to take some of the elements,how?
r = []
n = 3
for i in range(n):
    r.append(L[i])
print(r)
print()
# -- is too low....Python provides a Slice(':') operator, can greatly simplify the operation.

'''
L[a:b]  表示,从索引a开始取,直到索引b为止，但不包括索引b
L[a:b:c]表示,从索引a开始,直到索引b为止，每c个取一个,如果c为负数,表示倒序
如果索引是0，还可以省略
使用过程中注意索引位置
'''
print('L[4:8]       =', L[4:8])
print('L[:3]        =', L[:3])
#前10个数，每两个取一个：
print('L[:10:2]     =', L[:10:2])
#所有数，每5个取一个：
print('L[::5]       =', L[::5])
#甚至什么都不写，只写[:]就可以原样复制一个list：
print('L[:]         =', L[:])

#类似的，既然Python支持L[-1]取倒数第一个元素，那么它同样支持倒数切片，试试：
print('L[-8:-4]     =', L[-8:-4])
print('L[:-3]       =', L[:-3])
print('L[-1:-10:-2] =', L[-1:-10:-2])
print('L[::-5]      =', L[::-5])

print('L[-10:15:2]  =', L[-10:15:2])

print('---------------------------------------------------------------------------------------------------')


# ---------------------------------------------------------------------------------------------------
# 2.迭代 iteration
""""
如果给定一个list或tuple，我们可以通过for循环来遍历这个list或tuple，这种遍历我们称为迭代（Iteration）。
C和java的迭代是通过下标完成的, Python的for循环抽象程度要高于C和java的for循环，因为Python的for循环不仅可以用在list或tuple上，还可以作用在其他可迭代对象上。
list这种数据类型虽然有下标，但很多其他数据类型是没有下标的，但是，只要是可迭代对象，无论有无下标，都可以迭代，比如dict就可以迭代：
"""
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
    print(key, end = ' ')
print()

'''
因为dict的存储不是按照list的方式顺序排列，所以，迭代出的结果顺序很可能不一样。
默认情况下，dict迭代的是key。
如果要迭代value，可以用for value in d.values()，如果要同时迭代key和value，可以用for k, v in d.items()。
由于字符串也是可迭代对象，因此，也可以作用于for循环：
'''
for value in d.values():
    print(value, end = ' ')
print()

for k,v in d.items():
    print(k, '=',v,end = ' ')
print()

# string/list/tuple/dict/set 都是可迭代对象
#S = set("hgjskhfgjksd")     
S = {'v',12,'d'}
for s in S:
    print(s,end = ' ')
print()


"""
当我们使用for循环时，只要作用于一个可迭代对象，for循环就可以正常运行，而我们不太关心该对象究竟是list还是其他数据类型。
那么，如何判断一个对象是可迭代对象呢？方法是通过collections模块的Iterable类型判断：
"""
from collections import Iterable
print("isinstance('abc', Iterable)  =",isinstance('abc', Iterable)) # str是否可迭代
print("isinstance([1,2,3], Iterable)=",isinstance([1,2,3], Iterable)) # list是否可迭代
print("isinstance(123, Iterable)    =",isinstance(123, Iterable)) # number是否可迭代

'''
如果要对list实现类似Java那样的下标循环怎么办？
Python内置的enumerate函数可以把一个list变成索引-元素对，这样就可以在for循环中同时迭代索引和元素本身：
'''

for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)
print()
#上面的for循环里，同时引用了两个变量，在Python里是很常见的，比如下面的代码：
for x, y in [(1, 1), (2, 4), (3, 9)]:
    print(x, y)
print()
#任何可迭代对象都可以作用于for循环，包括我们自定义的数据类型，只要符合迭代条件，就可以使用for循环。


# ---------------------------------------------------------------------------------------------------
# 3.列表生成式 List Comprehensions
#列表生成式即List Comprehensions，是Python内置的非常简单却强大的可以用来创建list的生成式。

#if you want to generate list[1,2,3,4,5],shoud use list(range(1,6))
L = list(range(1,6))
print('L            =', L)
#But,if you want to generate list[1*1,2*2,3*3,4*4,5*5],how?
L = []
for i in range(1,6):
    L.append(i*i)
print('L            =', L)
# -- is too low....Python provides list Comprehensions, can generate the list.
L = [x * x for x in range(1, 6)]
print('L            =', L)

#if you want to filter out the even number
L = [x * x for x in range(1, 6) if x % 2 == 0]
print('L            =', L)

#使用两层循环，可以生成全排列：
L = [m + n for m in 'ABC' for n in 'XYZ']
print('L            =', L)

"""
三层和三层以上的循环就很少用到了
运用列表生成式，可以写出非常简洁的代码。例如，列出当前目录下的所有文件和目录名，可以通过一行代码实现：
"""
import os # 导入os模块，模块的概念后面讲到
L = [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
print('L            =', L)

#for循环其实可以同时使用两个甚至多个变量，比如dict的items()可以同时迭代key和value
#因此，列表生成式也可以使用两个变量来生成list：
d = {'x': 'A', 'y': 'B', 'z': 'C' }
#for k, v in d.items():
#    print(k, '=', v)
L = [k + '=' + v for k, v in d.items()]
print('L            =', L)

#把list中所有的字符串变成小写：
l = [s.lower() for s in L]
print('l            =', l)

"""
如果list中既包含字符串，又包含整数，由于非字符串类型没有lower()方法，所以列表生成式会报错
使用内建的isinstance函数可以判断一个变量是不是字符串
"""
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s, str)]
print('L1           =', L1)
print('L2           =', L2)

print()

# ---------------------------------------------------------------------------------------------------
# 4.生成器 
"""
通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。
而且，创建一个包含100万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。

所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？
这样就不必创建完整的list，从而节省大量的空间。
在Python中，这种一边循环一边计算的机制，称为生成器：generator。
"""
L = [x * x for x in range(5)]
g = (x * x for x in range(5))
print('L            =', L)
print('g            =', g)
'''
创建L和g的区别仅在于最外层的[]和()，L是一个list，而g是一个generator。
我们可以直接打印出list的每一个元素，但我们怎么打印出generator的每一个元素呢？
如果要一个一个打印出来，可以通过next()函数获得generator的下一个返回值：
'''
print(next(g),end=' ')
print(next(g),end=' ')
print(next(g),end=' ')
print(next(g),end=' ')
print(next(g),end=' ')
#print(next(g))     #StopIteration
print()
'''
generator保存的是算法，每次调用next(g)，就计算出g的下一个元素的值，直到计算到最后一个元素，没有更多的元素时，抛出StopIteration的错误。
当然，上面这种不断调用next(g)实在是太变态了，正确的方法是使用for循环，因为generator也是可迭代对象：
'''
g = (x * x for x in range(5))  #重新生成,因为之前执行了next(g)
for n in g:
    print(n,end=' ')
print('\n')
'''
所以，我们创建了一个generator后，基本上永远不会调用next()，而是通过for循环来迭代它，并且不需要关心StopIteration的错误。
generator非常强大。如果推算的算法比较复杂，用类似列表生成式的for循环无法实现的时候，还可以用函数来实现。
比如，著名的斐波拉契数列（Fibonacci），除第一个和第二个数外，任意一个数都可由前两个数相加得到：1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来，但是，用函数把它打印出来却很容易：
'''
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b,end=' ')
        a, b = b, a + b     #注意，赋值语句：b = b, a + b
        n = n + 1
    print()
    return 'done'
'''
注意，赋值语句：b = b, a + b, 相当于：
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
这里不必显式写出临时变量t就可以赋值。
'''
fib(10)
'''
仔细观察，可以看出，fib函数实际上是定义了斐波拉契数列的推算规则，可以从第一个元素开始，推算出后续任意的元素，这种逻辑其实非常类似generator。

也就是说，上面的函数和generator仅一步之遥。要把fib函数变成generator，只需要把print(b)改为yield b就可以了：
'''
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b     #注意，赋值语句：b = b, a + b
        n = n + 1
    return 'done'
'''
这就是定义generator的另一种方法。
如果一个函数定义中包含yield关键字，那么这个函数就不再是一个普通函数，而是一个generator

这里，最难理解的就是generator和函数的执行流程不一样。函数是顺序执行，遇到return语句或者最后一行函数语句就返回。
而变成generator的函数，在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行。
'''
g = fib(10)
print('g            =', g)
for n in g:
    print(n,end=' ')
print('\n')
'''
用for循环调用generator时，发现拿不到generator的return语句的返回值。
如果想要拿到返回值，必须捕获StopIteration错误，返回值包含在StopIteration的value中：
'''

g = fib(10)
while True:
     try:
         x = next(g)
         print('g:', x)
     except StopIteration as e:
         print('Generator return value:', e.value)
         break
print('\n')

# ---------------------------------------------------------------------------------------------------
# 5.迭代器 - Iterator       可迭代对象 - Iterable。
'''
我们已经知道，可以直接作用于for循环的数据类型有以下几种：
一类是集合数据类型，如list、tuple、dict、set、str等；
一类是generator，包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为 "可迭代对象"：Iterable。
'''

#判断一个对象是否是可迭代Iterable对象
from collections import Iterable
print('Iterable:')
print(isinstance((x for x in range(10)), Iterable))
print(isinstance([], Iterable))     #list
print(isinstance({}, Iterable))     #dict
print(isinstance({1,2}, Iterable))  #Set
print(isinstance((), Iterable))     #tuple
print(isinstance('abc', Iterable))  #str
print(isinstance(100, Iterable))    #number  False



'''
生成器不但可以作用于for循环，还可以被next()函数不断调用并返回下一个值，直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以被next()函数调用并不断返回下一个值的对象称为迭代器：Iterator。
生成器都是Iterator对象，但list、dict、str虽然是Iterable，却不是Iterator。
'''
#判断一个对象是否是迭代器Iterator对象
from collections import Iterator
print('\nIterator:')
print(isinstance((x for x in range(10)), Iterator))
print(isinstance([], Iterator))     #list   False    
print(isinstance({}, Iterator))     #dict   False
print(isinstance({1,2}, Iterator))  #Set    False
print(isinstance((), Iterator))     #tuple  False
print(isinstance('abc', Iterator))  #str    False

'''
把list、dict、str等Iterable变成Iterator可以使用iter()函数：
'''
print('\niter(object):')
print(isinstance(iter([]), Iterator))     #list   True    
print(isinstance(iter({}), Iterator))     #dict   True
print(isinstance(iter({1,2}), Iterator))  #Set    True
print(isinstance(iter(()), Iterator))     #tuple  True
print(isinstance(iter('abc'), Iterator))  #str    True

'''
你可能会问，为什么list、dict、str等数据类型不是Iterator？

这是因为Python的Iterator对象表示的是一个数据流，Iterator对象可以被next()函数调用并不断返回下一个数据，直到没有数据时抛出StopIteration错误。

可以把这个数据流看做是一个有序序列，但我们却不能提前知道序列的长度，只能不断通过next()函数实现按需计算下一个数据，所以Iterator的计算是惰性的，只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流，例如全体自然数。而使用list是永远不可能存储全体自然数的。
'''





